# -*- coding: UTF-8 -*-

from __future__ import absolute_import

import os
import sys
import copy
import urllib2
import zipfile

try:
    import json
except ImportError:
    import simplejson as json


#TODO:plugin manager widget
    #install from web
    #list local plugins
    #uninstall plugins


class ServiceLocator(object):
    '''
    Hold the services and allows the interaction between NINJA-IDE and plugins
    '''

    def __init__(self, services=None):
        self.__services = services if services else {}

    def get_service(self, name):
        return self.__services.get(name)

    def get_availables_services(self):
        return self.__services.keys()


'''
NINJA-IDE Plugin
my_plugin.plugin

{
  "module": "my_plugin",
  "class": "MyPluginExample",
  "authors": ["Martin Alderete <malderete@gmail.com>"],
  "version": "0.1",
  "description": "Este plugin es de prueba"
}

class MyPluginExample(Plugin):

    def initialize(self):
        #Configure the plugin using the NINJA-IDE API!!!
        self.editor_s = self.service_locator.get_service('editor')
        self.toolbar_s = self.service_locator.get_service('toolbar')
        self.toolbar_s.add_action(QAction(...........))
        self.appmenu_s = self.service_locator.get_service('appmenu')
        self.appmenu_s.add_menu(QMenu(......))
        #connect events!
        self.editor_s.editorKeyPressEvent.connect(self.my_plugin_key_pressed)

    def my_plugin_key_pressed(self, ...):
        print 'se apreto alguna tecla en el ide...'

'''
###############################################################################
# NINJA-IDE Plugin Manager
###############################################################################


class PluginManagerException(Exception):
    pass


#Singleton
__pluginManagerInstance = None


def PluginManager(*args, **kw):
    global __pluginManagerInstance
    if __pluginManagerInstance is None:
        __pluginManagerInstance = __PluginManager(*args, **kw)
    return __pluginManagerInstance


class __PluginManager(object):
    '''
    Plugin manager allows to load, unload, initialize plugins.
    '''
    #Extension of the NINJA-IDE plugin
    PLUGIN_EXTENSION = '.plugin'

    def __init__(self, plugins_dir, service_locator):
        '''
        @param plugins_dir: Path to search plugins.
        @param service_loctor: ServiceLocator object.
        '''

        self._service_locator = service_locator
        self._plugins_dir = plugins_dir
        self._errors = []
        #found plugins
        #example: ["logger", "my_plugin"]
        self._found_plugins = []
        #active plugins
        #example: {"logger": (LoggerIntance, metadata),
        #            "my_plugin": (MyPluginInstance, metadata)}
        self._active_plugins = {}

    def get_actives_plugins(self):
        '''
        Return a list the instances
        '''
        return [plugin[0] for plugin in self._active_plugins.values()]

    def __getitem__(self, plugin_name):
        '''
        Magic method to get a plugin instance
        from a given name.
        @Note: This method has the logic below.
        Check if the plugin is known,
        if it is active return it,
        otherwise, active it and return it.
        It is exception safe, if the plugin
        is not known return None.

        @param plugin_name: plugin name.

        @return: Plugin instance or None
        '''
        ext = self.PLUGIN_EXTENSION
        if not plugin_name.endswith(ext):
            plugin_name += ext

        if plugin_name in self._found_plugins:
            if not plugin_name in self._active_plugins:
                self.load(plugin_name)
            return self._active_plugins[plugin_name][0]
        return None

    def __contains__(self, plugin_name):
        '''
        Magic method to know whether the
        PluginManager contains
        a plugin with a given name.

        @param plugin_name: plugin name.

        @return: True or False.
        '''
        return plugin_name in self._found_plugins

    def __iter__(self):
        '''
        Magic method to iterate over all
        the plugin's names.

        @return: iterator.
        '''
        return iter(self._found_plugins)

    def __len__(self):
        '''
        Magic method to know the plugins
        quantity.
        @return: length.

        '''
        return len(self._found_plugins)

    def __nonzero__(self):
        '''
        Magic method to indicate that any
        instance must pass the if conditional
        if x:
        '''
        return True

    def get_plugin_name(self, file_name):
        '''
        Get the plugin's name from a file name.
        @param file_name: A file object name.
        @return: A plugin name from a file.
        '''
        plugin_file_name, file_ext = os.path.splitext(file_name)
        return plugin_file_name

    def list_plugins(self, dir_name):
        '''
        Crawl a directory and collect plugins.
        @return: List with plugin names.
        '''
        ext = self.PLUGIN_EXTENSION
        return [plug for plug in os.listdir(dir_name) if plug.endswith(ext)]

    def is_plugin_active(self, plugin_name):
        '''
        Check if a plugin is or not active
        @param plugin_name: Plugin name to check.
        @return: True or False
        '''
        return plugin_name in self._active_plugins

    def discover(self, path_plugins=None):
        '''
        Search all files in a directory
        and get the valid plugin's names.
        '''
        plugins_location = self._plugins_dir
        if path_plugins:
            plugins_location = path_plugins
        for file_name in self.list_plugins(plugins_location):
            #plugin_name = self.get_plugin_name(file_name)
            plugin_name = file_name
            if not plugin_name in self._found_plugins:
                self._found_plugins.append(plugin_name)

    def _load_module(self, module, klassname, path_location):
        old_syspath = copy.copy(sys.path)
        try:
            sys.path.append(path_location)
            module = __import__(module, globals(), locals(), ['plugin'])
            klass = getattr(module, klassname)
            #Instanciate the plugin
            plugin_instance = klass(self._service_locator)
            #return the plugin instance
            return plugin_instance
        except(ImportError, AttributeError), reason:
            raise PluginManagerException('Error loading "%s": %s' %\
                 (module, reason))
        finally:
            sys.path = old_syspath
        return None

    def load(self, plugin_name, plugin_path=None):
        ext = self.PLUGIN_EXTENSION
        plugin_location = self._plugins_dir
        if plugin_path:
            plugin_location = plugin_path
        plugin_filename = os.path.join(plugin_location, plugin_name)
        read = open(plugin_filename, 'r')
        plugin_structure = json.load(read)
        read.close()
        module = plugin_structure.get('module', None)
        klassname = plugin_structure.get('class', None)
        if module is not None and klassname is not None:
            try:
                plugin_instance = self._load_module(module,
                    klassname, plugin_location)
                #set a get_plugin method to get the reference to another plugin
                #setattr(plugin_instance, 'get_plugin', self.__getitem__)
                #call a special method *initialize* in the plugin!
                #print "Calling *initialize in the plugin: %s*" % plugin_name
                plugin_structure['name'] = plugin_name.replace(ext, '')
                plugin_instance.metadata = plugin_structure
                plugin_instance.initialize()
                #tuple (instance, metadata)
                plugin_metadata = (plugin_instance, plugin_structure)
                self._active_plugins[plugin_name] = plugin_metadata
            except (PluginManagerException, Exception), reason:
                print "Plugin Manager Error (%s): %s" % (plugin_name, reason)
                #remove the plugin because has errors
                self._found_plugins.remove(plugin_name)
                self._add_error(plugin_name.replace(ext, ''))
            else:
                print "successfuly initialized (%s)" % plugin_name

    def load_all(self):
        #Copy the list because may be we REMOVE item while iterate!
        found_plugins_aux = copy.copy(self._found_plugins)
        for plugin_name in found_plugins_aux:
            self.load(plugin_name)

    def load_all_external(self, plugin_path):
        #Copy the list because may be we REMOVE item while iterate!
        found_plugins_aux = copy.copy(self._found_plugins)
        for plugin_name in found_plugins_aux:
            self.load(plugin_name, plugin_path)

    def unload(self, plugin_name):
        try:
            plugin_object = self._active_plugins[plugin_name][0]
            #call a special method *finish* in the plugin!
            plugin_object.finish()
            del self._active_plugins[plugin_name]
        except Exception, reason:
            print "Error finishing plugin (%s):  %s" % (plugin_name, reason)
        finally:
            print "successfuly finished (%s)" % plugin_name

    def unload_all(self):
        #Copy the list because may be we REMOVE item while iterate!
        active_plugins_aux = copy.copy(self._active_plugins)
        for plugin_name in active_plugins_aux:
            self.unload(plugin_name)

    def shutdown(self):
        self.unload_all()

    def _add_error(self, plugin_name):
        self._errors.append(plugin_name)

    def get_errors(self):
        """
        Returns a comma separated values of errors
        """
        return ', '.join([p for p in self._errors])
    errors = property(get_errors)


def available_plugins():
    '''
    Returns a dict with availables plugins in NINJA-IDE web page
    '''
    PLUGINS_WEB = 'http://plugins.ninja-ide.googlecode.com/hg/descriptor.json'
    descriptor = urllib2.urlopen(PLUGINS_WEB)
    plugins = json.load(descriptor)
    return plugins


def local_plugins():
    '''
    Returns the local plugins
    '''
    DESCRIPTOR = resources.descriptor
    if not os.path.isfile(DESCRIPTOR):
        return {}
    descriptor = open(DESCRIPTOR, 'r')
    plugins = json.load(descriptor)
    descriptor.close()
    return plugins


def download_plugin(file_):
    '''
    Download a plugin specified by file_
    '''
    fileName = resources.createpath(resources.plugins, os.path.basename(file_))
    content = urllib2.urlopen(file_)
    f = open(fileName, 'wb')
    f.write(content.read())
    f.close()
    zipFile = zipfile.ZipFile(fileName, 'r')
    zipFile.extractall(resources.plugins)
    zipFile.close()
    os.remove(fileName)


def update_local_descriptor(plugins):
    '''
    updates the local plugin description
    '''
    structure = {}
    if os.path.isfile(resources.descriptor):
        read = open(resources.descriptor, 'r')
        structure = json.load(read)
        read.close()
    for plug in plugins:
        structure[plug[0]] = [plug[1], plug[2], plug[3]]
    descriptor = open(resources.descriptor, 'w')
    json.dump(structure, descriptor, indent=2)


def uninstall_plugin(plug):
    fileName = os.path.basename(plug[3])
    fileName = os.path.splitext(fileName)[0]
    fileName = resources.createpath(resources.plugins, fileName + '.json')
    descriptor = open(fileName, 'r')
    plugin = json.load(descriptor)
    descriptor.close()
    module = plugin.get('module', False)
    if module:
        pluginDir = resources.createpath(resources.plugins, module)
        folders = [pluginDir]
        for root, dirs, files in os.walk(pluginDir):
            pluginFiles = [resources.createpath(root, f) for f in files]
            map(os.remove, pluginFiles)
            folders += [resources.createpath(root, d) for d in dirs]
        folders.reverse()
        for f in folders:
            if os.path.isdir(f):
                os.removedirs(f)
        os.remove(fileName)
    structure = {}
    if os.path.isfile(resources.descriptor):
        read = open(resources.descriptor, 'r')
        structure = json.load(read)
        read.close()
    structure.pop(module)
    descriptor = open(resources.descriptor, 'w')
    json.dump(structure, descriptor, indent=2)


###############################################################################
# Module Test
###############################################################################

if __name__ == '__main__':
    from ninja_ide import resources
    folders = resources.PLUGINS
    services = {}
    sl = ServiceLocator(services)
    pm = PluginManager(folders, sl)
    #There are not plugins yet...lets discover
    pm.discover()
    print "listing plugins names..."
    for p in pm:
        print p
    print "Activating plugins..."
    pm.load_all()
    print "Plugins already actives..."
    print pm.get_actives_plugins()
